package com.iwdnb.gkgz.application.service.impl;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.system.SystemUtil;
import com.google.common.collect.Lists;
import com.iwdnb.bmnf.common.dingtalk.DingTalkService;
import com.iwdnb.bmnf.common.excel.core.ExcelService;
import com.iwdnb.bmnf.common.service.CacheService;
import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.bmnf.common.utils.CompressUtils;
import com.iwdnb.bmnf.common.utils.EnumUtils;
import com.iwdnb.bmnf.common.utils.Timer;
import com.iwdnb.gkgz.application.kline.InvertHammer;
import com.iwdnb.gkgz.application.kline.ShapeEnum;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.application.model.vo.StrategyTradeScore;
import com.iwdnb.gkgz.application.model.vo.StrategyTradeScore.TradeScoreItem;
import com.iwdnb.gkgz.application.model.vo.StrategyTradeScoreInfo;
import com.iwdnb.gkgz.application.model.vo.StrategyTradeStatisticData;
import com.iwdnb.gkgz.application.service.StockRangeService;
import com.iwdnb.gkgz.application.service.StrategyTradeService;
import com.iwdnb.gkgz.application.service.StrategyTradeStatisticService;
import com.iwdnb.gkgz.application.utils.StockScoreUtils;
import com.iwdnb.gkgz.common.constant.CacheConstants;
import com.iwdnb.gkgz.common.enums.StockImageProfitTypeEnums;
import com.iwdnb.gkgz.common.model.dto.Stock;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StrategyImageStatisticDTO;
import com.iwdnb.gkgz.common.model.dto.StrategyParamDTO;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.quota.HullMovingAverage;
import com.iwdnb.gkgz.common.quota.HullMovingAverage.HullAverageData;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import com.iwdnb.gkgz.common.utils.WorkDayUtils;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockDayDataRepository;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockRepository;
import com.iwdnb.wwzy.dao.MetaDataDAO;
import com.iwdnb.wwzy.model.query.MetaQuery;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.iwdnb.gkgz.common.constant.GkgzConstants.OBJECT_CODE_STRATEGY_PARAM;
import static com.iwdnb.gkgz.common.constant.GkgzConstants.OBJECT_CODE_STRATEGY_TRADE;
import static com.iwdnb.gkgz.common.constant.SqlConstants.STOCK_IMAGE_STATISTIC;

@Service
@Slf4j
public class StrategyTradeServiceImpl implements StrategyTradeService {

    @Autowired
    private StockDayDataRepository stockDayDataRepository;
    @Autowired
    private MetaDataDAO metaDataDAO;
    @Autowired
    private DingTalkService dingTalkService;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private StockRepository stockRepository;
    @Autowired
    private StockRangeService stockRangeService;
    @Autowired
    private ExcelService excelService;
    @Autowired
    private StrategyTradeStatisticService strategyTradeStatisticService;
    @Autowired
    private Executor executor;
    @Autowired
    InvertHammer invertHammer;

    private static final String UPDATE_SCORE_SQL =
        "UPDATE gkgz_strategy_trade SET  score = :score, modifier = 0, modify_time = now() "
            + "WHERE gkgz_strategy_trade.is_deleted = 'n' AND id = :id";

    private String DING_TALK_URL = "https://oapi.dingtalk"
        + ".com/robot/send?access_token=62fdde2e47cc3876b4723242adfd599c7e7e5712f9952419f1f0be261780b5ea";
    private static final String startDateStr = "2015-01-01 00:00:00";

    private static final String TEMP_DIR = SystemUtil.get(SystemUtil.USER_DIR) + File.separator + "temp"
        + File.separator;

    static {
        FileUtil.file(TEMP_DIR).mkdirs();
    }

    @Override
    public List<StrategyTradeDTO> queryStrategyTradeList(String code, String uuid) {
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("code", code);
        metaQuery.addParam("uuid", uuid);
        return metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE, metaQuery, StrategyTradeDTO.class);
    }

    @Override
    public void doStrategyTrade() {
        //买入,暂时不实现。
        doBuyStrategy();
        //卖出
        doSellStrategy();
    }

    @Override
    public void syncStrategyStockPrice(Stock stock) {
        //同步策略交易实时价格
        String code = stock.getCode();
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("status", "buy");
        metaQuery.addParam("code", code);
        StrategyTradeDTO strategyTradeDTO = metaDataDAO.get(OBJECT_CODE_STRATEGY_TRADE, metaQuery,
            StrategyTradeDTO.class);
        if (Objects.isNull(strategyTradeDTO)) {
            return;
        }
        strategyTradeDTO.setRealtimePrice(stock.getPrice());
        BigDecimal range = stock.getPrice().subtract(strategyTradeDTO.getBuyPrice());
        BigDecimal rate = range.multiply(new BigDecimal(100)).divide(strategyTradeDTO.getBuyPrice(),
            BigDecimal.ROUND_HALF_DOWN).setScale(2, RoundingMode.HALF_DOWN);
        strategyTradeDTO.setRealtimeRate(rate);
        if (stock.getPrice().compareTo(strategyTradeDTO.getRangeMaxPrice()) > 0) {
            strategyTradeDTO.setRangeMaxPrice(stock.getPrice());
        }
        int holdDay = WorkDayUtils.betweenDay(strategyTradeDTO.getBuyDate(), new Date());
        strategyTradeDTO.setHoldDay(holdDay);
        metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTO);
    }

    @Override
    public void addStrategyTradeStockData(AddStrategyTradeStockDataRequest request) {
        String codes = request.getCodes();
        String uuid = request.getUuid();
        String shapes = request.getShapes();
        String backFlag = request.getBackFlag();
        List<String> codeList = Lists.newArrayList(codes.split(","));
        for (int i = 0; i < codeList.size(); i++) {
            String code = codeList.get(i);
            List<StrategyTradeDTO> strategyTradeDTOList = queryStrategyTradeList(code, uuid);
            if (CollectionUtils.isNotEmpty(strategyTradeDTOList)) {
                log.warn("{}-{}策略数据已存在,不重复添加", code, uuid);
                continue;
            }
            log.info("addStrategyTradeStockData:{}-{}", code, i);
            executor.execute(() -> {
                addStrategyTradeStockData(code, uuid, shapes, backFlag);
            });
        }

    }

    private void addStrategyTradeStockData(String code, String uuid, String shapes, String backFlag) {
        //获取符合股票特征的股票k线数据
        String endDateStr = DateUtil.formatDateTime(new Date());
        List<StockDayData> stockDayDataList = stockDayDataRepository.queryAllByDateRange(code, startDateStr,
            endDateStr);
        if (CollectionUtils.isEmpty(stockDayDataList)) {
            return;
        }
        String[] shapeArray = shapes.split(",");
        List<String> matchDateList = new ArrayList<>();
        //多个形态求交集
        for (String shape : shapeArray) {
            ShapeEnum shapeEnum = EnumUtils.getEnumObjectByCode(ShapeEnum.class, shape);
            List<StockDayData> matchList = shapeEnum.getShape().getMatchData(code, stockDayDataList, matchDateList);
            List<String> dateList = matchList.stream().map(StockDayData::getDate).collect(Collectors.toList());
            log.info("{}:{}形态匹配买点数:{}，日期集:{}", code, shape, dateList.size(), dateList);
            if (CollectionUtils.isEmpty(matchDateList)) {
                matchDateList.addAll(dateList);
            } else {
                //日期合并
                matchDateList = mergeDateList(matchDateList, dateList);
                log.info("{}:交集形态买点数:{},日期集:{}", code, matchDateList.size(), matchDateList);

            }
        }
        if (CollectionUtils.isEmpty(matchDateList)) {
            log.info("{}没有匹配的买点日期，忽略", code);
            if (BooleanUtils.isTrue(backFlag)) {
                addNotMatchTradeRecord(code, uuid);
                doBackStrategyTrade(uuid, code);
            }
            return;
        }
        Map<String, StockDayData> dayDataMap = stockDayDataList.stream().collect(
            Collectors.toMap(StockDayData::getDate, Function.identity()));
        //持久化股票策略买点数据
        Stock stock = stockRepository.findByCode(code);
        List<StrategyTradeDTO> strategyTradeDTOList = new ArrayList<>();
        for (String date : matchDateList) {
            StockDayData stockDayData = dayDataMap.get(date);
            StrategyTradeDTO strategyTradeDTO = new StrategyTradeDTO();
            strategyTradeDTO.setCode(code);
            strategyTradeDTO.setUuid(uuid);
            strategyTradeDTO.setName(stock.getName());
            strategyTradeDTO.setStatus("buy");
            strategyTradeDTO.setRangeMaxPrice(stockDayData.getClosePrice());
            strategyTradeDTO.setRealtimePrice(stockDayData.getClosePrice());
            strategyTradeDTO.setBuyPrice(stockDayData.getClosePrice());
            strategyTradeDTO.setBuyMinPrice(stockDayData.getMinPrice());
            strategyTradeDTO.setBuyDate(DateUtil.parseDate(stockDayData.getDate()));
            strategyTradeDTO.setHoldDay(0);
            strategyTradeDTO.setBackFlag("y");
            strategyTradeDTO.setRealtimeRate(BigDecimal.ZERO);
            strategyTradeDTOList.add(strategyTradeDTO);
        }
        metaDataDAO.insert(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTOList);
        //直接回测
        if (BooleanUtils.isTrue(backFlag)) {
            doBackStrategyTrade(uuid, code);
        }
    }

    private void addNotMatchTradeRecord(String code, String uuid) {
        StrategyTradeDTO strategyTradeDTO = new StrategyTradeDTO();
        strategyTradeDTO.setCode(code);
        strategyTradeDTO.setUuid(uuid);
        Stock stock = stockRepository.findByCode(code);
        strategyTradeDTO.setName(stock.getName());
        strategyTradeDTO.setBackFlag("n");
        strategyTradeDTO.setStatus("disable");
        strategyTradeDTO.setStrategyDescription("没有匹配的买点日期，忽略");
    }

    private List<String> mergeDateList(List<String> matchDateList, List<String> dateList) {
        List<String> list = new ArrayList<>();
        for (String d : matchDateList) {
            Date date = DateUtil.parseDate(d);
            for (String d2 : dateList) {
                Date date2 = DateUtil.parseDate(d2);
                Long time = Math.abs(date.getTime() - date2.getTime());
                //2个买点间隔2天之内
                if (time < 3 * 86400 * 1000) {
                    if (date.before(date2)) {
                        list.add(d);
                    } else {
                        list.add(d2);
                    }
                    break;
                }
            }
        }
        return list;
    }

    @Override
    public void doBackStrategyTrade(String uuid, String code) {
        MetaQuery paramQuery = new MetaQuery();
        paramQuery.addParam("uuid", uuid);
        paramQuery.addParam("code", code);
        List<StrategyTradeDTO> list = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE, paramQuery,
            StrategyTradeDTO.class);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        List<StockDayData> stockDayDataList = stockDayDataRepository.queryAll(code);
        BackStrategyParam param = buildBackStrategyParam(code, stockDayDataList);
        for (StrategyTradeDTO strategyTradeDTO : list) {
            strategyTradeDTO.setBackFlag("y");
            strategyTradeDTO.setStatus("buy");
            strategyTradeDTO.setSellPrice(null);
            strategyTradeDTO.setSellDate(null);
            strategyTradeDTO.setHoldDay(0);
            strategyTradeDTO.setRangeMaxPrice(BigDecimal.ZERO);
            strategyTradeDTO.setRealtimePrice(BigDecimal.ZERO);
            strategyTradeDTO.setRealtimeRate(strategyTradeDTO.getBuyPrice());
            strategyTradeDTO.setProfitRate(BigDecimal.ZERO);
            strategyTradeDTO.setProfitAmount(BigDecimal.ZERO);
            strategyTradeDTO.setStrategyDescription(" ");
        }
        Date oldSellDate = null;
        for (StrategyTradeDTO strategyTradeDTO : list) {
            //如果策略交易在上一个交易周期内,忽略
            if (Objects.nonNull(oldSellDate) && strategyTradeDTO.getBuyDate().before(oldSellDate)) {
                strategyTradeDTO.setStatus("disable");
                strategyTradeDTO.setStrategyDescription("在前一次交易周期中,忽略");
                //strategyTradeDTO.setSellDate(oldSellDate);
                //metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTO);
                continue;
            }
            if (BigDecimalUtils.isLe(strategyTradeDTO.getBuyPrice(), new BigDecimal(1))) {
                strategyTradeDTO.setStatus("disable");
                strategyTradeDTO.setStrategyDescription("买入股价<1,复权数据有误,不进行回测");
                //metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTO);
                continue;
            }
            Date begin = strategyTradeDTO.getBuyDate();
            Date end = DateUtil.offsetDay(begin, 500);

            List<StockDayData> subList = StockUtils.getStockDayDataListByDateRange(stockDayDataList, begin, end);
            param.setAfterDayDataList(subList);
            doBackStrategyTrade(strategyTradeDTO, param);
            //延时1秒，修复卖出买入同一天的问题
            oldSellDate = DateUtil.offsetSecond(strategyTradeDTO.getSellDate(), 1);
        }
        metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, list);
    }

    private BackStrategyParam buildBackStrategyParam(String code, List<StockDayData> stockDayDataList) {
        BackStrategyParam param = new BackStrategyParam();
        List<String> highDateList = stockRangeService.queryBollingAndHighPriceAndBiasDateList(code);
        param.setHighDateList(highDateList);
        List<HullAverageData> thirtyAverageDataList = HullMovingAverage.calculateHMA(stockDayDataList, 30);
        param.setAverageDataList(thirtyAverageDataList);
        param.setStockDayDataList(stockDayDataList);
        return param;
    }

    /**
     * 具体回测方法
     *
     * @param strategyTradeDTO
     * @param param
     */
    public void doBackStrategyTrade(StrategyTradeDTO strategyTradeDTO, BackStrategyParam param) {
        List<StockDayData> stockDayDataList = param.getAfterDayDataList();
        MetaQuery paramQuery = new MetaQuery();
        paramQuery.addParam("uuid", strategyTradeDTO.getUuid());
        StrategyParamDTO strategyParamDTO = metaDataDAO.get(OBJECT_CODE_STRATEGY_PARAM, paramQuery,
            StrategyParamDTO.class);

        for (int i = 0; i < stockDayDataList.size(); i++) {
            StockDayData stockDayData = stockDayDataList.get(i);
            stockDayData.setIndex(i);
            BigDecimal realtimePrice = stockDayData.getClosePrice();
            strategyTradeDTO.setRealtimePrice(realtimePrice);
            BigDecimal range = realtimePrice.subtract(strategyTradeDTO.getBuyPrice());
            BigDecimal rate = BigDecimalUtils.divideToPrecent(range, strategyTradeDTO.getBuyPrice());
            strategyTradeDTO.setRealtimeRate(rate);
            if (realtimePrice.compareTo(strategyTradeDTO.getRangeMaxPrice()) > 0) {
                strategyTradeDTO.setRangeMaxPrice(realtimePrice);
            }
            Date sellDate = DateUtil.parseDate(stockDayData.getDate());
            StrategySellInfo strategySellInfo = checkStrategy(realtimePrice, sellDate, strategyTradeDTO,
                strategyParamDTO, stockDayData, stockDayDataList, i, param);
            //不匹配卖出,直接跳过
            if (!strategySellInfo.matchSell) {
                continue;
            }
            //卖出股票
            sellStock(realtimePrice, sellDate, strategyTradeDTO, strategyParamDTO, strategySellInfo, false);
            break;
        }
    }

    @Override
    public void deleteStrategyTrades(String uuid, List<String> codeList) {
        for (String code : codeList) {
            MetaQuery paramQuery = new MetaQuery();
            paramQuery.addParam("uuid", uuid);
            paramQuery.addParam("code", code);
            List<StrategyTradeDTO> list = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE, paramQuery,
                StrategyTradeDTO.class);
            if (CollectionUtils.isEmpty(list)) {
                continue;
            }
            List<Long> ids = list.stream().map(StrategyTradeDTO::getId).collect(Collectors.toList());
            metaDataDAO.delete(OBJECT_CODE_STRATEGY_TRADE, ids);
        }
    }

    @Override
    public List<StrategyImageStatisticDTO> doStockImageStatistic(String strategy, String profitType) {
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("strategy", strategy);
        List<StrategyTradeDTO> strategyTradeDTOList = metaDataDAO.queryBySql(OBJECT_CODE_STRATEGY_TRADE,
            STOCK_IMAGE_STATISTIC, metaQuery, StrategyTradeDTO.class);
        Map<String, List<StrategyTradeDTO>> map = strategyTradeDTOList.stream().collect(
            Collectors.groupingBy(StrategyTradeDTO::getImageCode));
        Map<String, StrategyImageStatisticDTO> strategyImageStatisticDTOMap = new HashMap<>();
        for (Entry<String, List<StrategyTradeDTO>> entry : map.entrySet()) {
            StrategyImageStatisticDTO statisticDTO = strategyImageStatisticDTOMap.get(entry.getKey());
            if (Objects.isNull(statisticDTO)) {
                statisticDTO = new StrategyImageStatisticDTO();
                statisticDTO.setImageCode(entry.getKey());
                strategyImageStatisticDTOMap.put(entry.getKey(), statisticDTO);
            }
            for (StrategyTradeDTO strategyTradeDTO : entry.getValue()) {
                if (BooleanUtils.isTrue(strategyTradeDTO.getWinFlag())) {
                    statisticDTO.setSuccessCount(strategyTradeDTO.getCount());
                    statisticDTO.setSuccessSum(strategyTradeDTO.getSum());
                } else {
                    statisticDTO.setFailCount(strategyTradeDTO.getCount());
                    statisticDTO.setFailSum(strategyTradeDTO.getSum());
                }
                statisticDTO.caculate();

            }
        }
        List<StrategyImageStatisticDTO> strategyImageStatisticDTOList = Lists.newArrayList(
            strategyImageStatisticDTOMap.values());
        //过滤出有效的图形
        strategyImageStatisticDTOList = strategyImageStatisticDTOList.stream().filter(
            t -> BooleanUtils.isTrue(t.getValid())).collect(Collectors.toList());
        printWinLossLog(strategyImageStatisticDTOList);
        //根据类型过滤
        if (StringUtils.isNotBlank(profitType)) {
            List<String> typeList = Lists.newArrayList(profitType.split(","));
            strategyImageStatisticDTOList = strategyImageStatisticDTOList.stream().filter(
                t -> typeList.contains(t.getProfitType())).collect(Collectors.toList());
        }
        Collections.sort(strategyImageStatisticDTOList, (o1, o2) ->
            o2.getTotalRate().doubleValue() > o1.getTotalRate().doubleValue() ? 1 : -1);
        try {
            String resultExcelFile = TEMP_DIR + "stockImageProfit_" + DateUtil.format(new Date(),
                DatePattern.PURE_DATETIME_PATTERN) + ".xlsx";
            excelService.exportExcel(strategyImageStatisticDTOList, new FileOutputStream(resultExcelFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return strategyImageStatisticDTOList;
    }

    @Override
    public List<StrategyTradeStatisticData> statisticTradeList(List<String> groupList, String loadDataFlag) {
        String cacheKey = "statisticTradeList";
        String content = cacheService.get(cacheKey);
        List<StrategyTradeDTO> strategyTradeList = null;
        if (BooleanUtils.isTrue(loadDataFlag) || StringUtils.isBlank(content)) {
            MetaQuery metaQuery = new MetaQuery();
            metaQuery.addParam("uuid", "tulongdao");
            metaQuery.addParam("status", "sell");
            strategyTradeList = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE,
                metaQuery, StrategyTradeDTO.class);
            content = CompressUtils.compress(JSON.toJSONString(strategyTradeList));
            cacheService.put(cacheKey, content);
        } else {
            content = CompressUtils.unCompress(content);
            strategyTradeList = JSON.parseArray(content, StrategyTradeDTO.class);
        }
        if (CollectionUtils.isEmpty(strategyTradeList)) {
            log.error("strategyTradeList is empty,return");
            return null;
        }
        String firstGroup = groupList.get(0);
        Map<String, List<StrategyTradeDTO>> strategyTradeMap = StockScoreUtils.statisticTradeList(
            firstGroup, null, strategyTradeList);
        if (groupList.size() > 1) {
            for (int i = 1; i < groupList.size(); i++) {
                String group = groupList.get(i);
                Map<String, List<StrategyTradeDTO>> subAllMap = new LinkedHashMap<>();
                for (Entry<String, List<StrategyTradeDTO>> entry : strategyTradeMap.entrySet()) {
                    String oldGroup = entry.getKey();
                    List<StrategyTradeDTO> subList = strategyTradeMap.get(oldGroup);
                    Map<String, List<StrategyTradeDTO>> subMap = StockScoreUtils.statisticTradeList(group,
                        oldGroup, subList);
                    subAllMap.putAll(subMap);
                }
                //重新设置策略统计map,继续下次递归统计
                strategyTradeMap = subAllMap;
            }
        }
        List<StrategyTradeStatisticData> statisticDataList = new ArrayList<>();
        for (Entry<String, List<StrategyTradeDTO>> entry : strategyTradeMap.entrySet()) {
            StrategyTradeStatisticData statisticData = statisticStrategyTradeList(entry.getKey(), entry.getValue());
            if (statisticData.getCount() > 20) {
                statisticDataList.add(statisticData);
            }
        }
        if (CollectionUtils.isNotEmpty(statisticDataList)) {
            Collections.sort(statisticDataList,
                (o1, o2) -> o2.getAvgRate().doubleValue() > o1.getAvgRate().doubleValue() ? 1 : -1);
        }
        return statisticDataList;
    }

    @Override
    public List<StrategyTradeScore> initTradeScore(List<String> groupList) {
        String cacheKey = CacheConstants.TULONGDAO_TRADE_SCORE_CACHE_KEY;
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("uuid", "tulongdao");
        metaQuery.addParam("status", "sell");
        List<StrategyTradeDTO> strategyTradeList = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE,
            metaQuery, StrategyTradeDTO.class);
        if (CollectionUtils.isEmpty(strategyTradeList)) {
            log.error("strategyTradeList is empty,return");
            return null;
        }
        List<StrategyTradeScore> strategyTradeScoreList = new ArrayList<>();
        for (int i = 0; i < groupList.size(); i++) {
            String group = groupList.get(i);
            Map<String, List<StrategyTradeDTO>> statisticTradeMap = StockScoreUtils.statisticTradeList(
                group,
                null, strategyTradeList);
            StrategyTradeScore strategyTradeScore = formatTradeScore(group, statisticTradeMap, true);
            strategyTradeScoreList.add(strategyTradeScore);
        }
        cacheService.put(cacheKey, JSON.toJSONString(strategyTradeScoreList));
        return strategyTradeScoreList;
    }

    @Override
    public StrategyTradeScoreInfo calculateTradeScore(StrategyTradeDTO strategyTradeDTO,
        List<StrategyTradeScore> tradeScoreList) {
        if (CollectionUtils.isEmpty(tradeScoreList)) {
            tradeScoreList = getInitTradeScoreList();
        }
        return StockScoreUtils.calculateTradeScore(strategyTradeDTO, tradeScoreList);
    }

    @Override
    public void calculateAllTradeScore() {
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("uuid", "tulongdao");
        metaQuery.addParam("status", "sell");
        List<StrategyTradeDTO> strategyTradeList = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE,
            metaQuery, StrategyTradeDTO.class);
        if (CollectionUtils.isEmpty(strategyTradeList)) {
            log.error("strategyTradeList is empty,return");
            return;
        }
        List<StrategyTradeScore> tradeScoreList = getInitTradeScoreList();
        AtomicInteger count = new AtomicInteger();
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeList) {
            executor.execute(() -> {
                int index = count.incrementAndGet();
                StrategyTradeScoreInfo strategyTradeScoreInfo = calculateTradeScore(strategyTradeDTO, tradeScoreList);
                strategyTradeDTO.setScore(strategyTradeScoreInfo.getScore());
                metaDataDAO.updateBySql(UPDATE_SCORE_SQL, strategyTradeDTO);
                log.info("calculateTradeScore-{}-{} success,score:{},profitRate:{}", index,
                    strategyTradeDTO.getId(), strategyTradeDTO.getScore(), strategyTradeDTO.getProfitRate());
            });
        }
    }

    private List<StrategyTradeScore> getInitTradeScoreList() {
        String cacheKey = CacheConstants.TULONGDAO_TRADE_SCORE_CACHE_KEY;
        String content = cacheService.get(cacheKey);
        if (StringUtils.isBlank(content)) {
            log.error("trade score config is not inited,return");
            return null;
        }
        return JSON.parseArray(content, StrategyTradeScore.class);
    }

    private StrategyTradeScore formatTradeScore(String group, Map<String, List<StrategyTradeDTO>> tradeMap,
        boolean initFlag) {
        StrategyTradeScore strategyTradeScore = new StrategyTradeScore();
        strategyTradeScore.setGroup(group);
        List<TradeScoreItem> items = new ArrayList<>();
        for (Entry<String, List<StrategyTradeDTO>> entry : tradeMap.entrySet()) {
            StrategyTradeStatisticData statisticData = statisticStrategyTradeList(entry.getKey(), entry.getValue());
            TradeScoreItem item = new TradeScoreItem();
            item.setLabel(statisticData.getGroup1());
            item.setCount(statisticData.getCount());
            item.setSumRate(statisticData.getSumRate());
            item.setAvgRate(statisticData.getAvgRate());
            if (!initFlag || statisticData.getCount() > 20) {
                items.add(item);
            }
        }
        strategyTradeScore.setItems(items);
        return strategyTradeScore;
    }

    private StrategyTradeStatisticData statisticStrategyTradeList(String group,
        List<StrategyTradeDTO> strategyTradeList) {
        StrategyTradeStatisticData data = new StrategyTradeStatisticData();
        formatGroup(group, data);
        data.setCount(strategyTradeList.size());
        if (data.getCount() > 0) {
            data.setSumRate(
                strategyTradeList.stream().map(t -> t.getProfitRate()).reduce(BigDecimal.ZERO, BigDecimal::add));
            data.setAvgRate(BigDecimalUtils.divide(data.getSumRate(), new BigDecimal(data.getCount())));
        }
        List<StrategyTradeDTO> winList = strategyTradeList.stream().filter(t -> BooleanUtils.isTrue(t.getWinFlag()))
            .collect(Collectors.toList());
        data.setWinCount(winList.size());
        if (data.getWinCount() > 0) {
            data.setWinSumRate(winList.stream().map(t -> t.getProfitRate()).reduce(BigDecimal.ZERO, BigDecimal::add));
            data.setWinAvgRate(BigDecimalUtils.divide(data.getWinSumRate(), new BigDecimal(data.getWinCount())));
        }
        List<StrategyTradeDTO> lossList = strategyTradeList.stream().filter(t -> BooleanUtils.isFalse(t.getWinFlag()))
            .collect(Collectors.toList());
        data.setLossCount(lossList.size());
        if (data.getLossCount() > 0) {
            data.setLossSumRate(lossList.stream().map(t -> t.getProfitRate()).reduce(BigDecimal.ZERO, BigDecimal::add));
            data.setLossAvgRate(BigDecimalUtils.divide(data.getLossSumRate(), new BigDecimal(data.getLossCount())));
        }
        return data;
    }

    private void formatGroup(String group, StrategyTradeStatisticData data) {
        String[] groupArray = group.split(",");
        data.setGroupSize(groupArray.length);
        for (int i = 0; i < groupArray.length; i++) {
            String g = groupArray[i];
            String[] array = g.split(":");
            String value = array[1];
            switch (i) {
                case 0:
                    data.setGroup1(value);
                    break;
                case 1:
                    data.setGroup2(value);
                    break;
                case 2:
                    data.setGroup3(value);
                    break;
                case 3:
                    data.setGroup4(value);
                    break;
                case 4:
                    data.setGroup5(value);
                    break;
                default:
            }
        }
    }

    private void printWinLossLog(List<StrategyImageStatisticDTO> strategyImageStatisticDTOList) {
        printWinLossLog(strategyImageStatisticDTOList, StockImageProfitTypeEnums.WIN.getCode());
        printWinLossLog(strategyImageStatisticDTOList, StockImageProfitTypeEnums.ALL_WIN.getCode());
        printWinLossLog(strategyImageStatisticDTOList, StockImageProfitTypeEnums.LOSS.getCode());
        printWinLossLog(strategyImageStatisticDTOList, StockImageProfitTypeEnums.ALL_LOSS.getCode());

    }

    private void printWinLossLog(List<StrategyImageStatisticDTO> strategyImageStatisticDTOList, String profitType) {
        List<String> imageCodes = strategyImageStatisticDTOList.stream().filter(
                t -> profitType.equals(t.getProfitType()))
            .map(StrategyImageStatisticDTO::getImageCode).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(imageCodes)) {
            log.info("{} is empty", profitType);
            return;
        }
        log.info("{} size:{},info:{}", profitType, StringUtils.join(imageCodes, ","), imageCodes.size());

    }

    private void doSellStrategy() {
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("status", "buy");
        metaQuery.addParam("backFlag", "n");
        List<StrategyTradeDTO> strategyTradeDTOList = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE,
            metaQuery, StrategyTradeDTO.class);
        if (CollectionUtils.isEmpty(strategyTradeDTOList)) {
            return;
        }
        Map<String, StrategyParamDTO> strategyParamMap = new HashMap<>();
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            String uuid = strategyTradeDTO.getUuid();
            StrategyParamDTO strategyParamDTO = strategyParamMap.get(uuid);
            if (strategyParamDTO == null) {
                MetaQuery paramQuery = new MetaQuery();
                paramQuery.addParam("uuid", uuid);
                strategyParamDTO = metaDataDAO.get(OBJECT_CODE_STRATEGY_PARAM, paramQuery, StrategyParamDTO.class);
                strategyParamMap.put(uuid, strategyParamDTO);
            }
            BigDecimal realtimePrice = strategyTradeDTO.getRealtimePrice();
            Date sellDate = new Date();
            StrategySellInfo strategySellInfo = checkStrategy(realtimePrice, sellDate, strategyTradeDTO,
                strategyParamDTO, null, null, 0, new BackStrategyParam());
            //不匹配卖出,直接跳过
            if (!strategySellInfo.matchSell) {
                continue;
            }
            //卖出股票
            sellStock(realtimePrice, sellDate, strategyTradeDTO, strategyParamDTO, strategySellInfo, true);
        }
    }

    private StrategyTradeDTO sellStock(BigDecimal realtimePrice, Date sellDate, StrategyTradeDTO strategyTradeDTO,
        StrategyParamDTO strategyParamDTO, StrategySellInfo strategySellInfo, boolean sendDingtalk) {
        if (BigDecimalUtils.isLe(realtimePrice, new BigDecimal(1))) {
            strategyTradeDTO.setStatus("disable");
            strategyTradeDTO.setStrategyDescription("股价<1,复权数据有误,不进行回测");
            //metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTO);
            return strategyTradeDTO;
        }
        log.info("{}-{}-{} 匹配卖出规则:{},买入股价:{}", DateUtil.formatDate(sellDate), strategyTradeDTO.getCode(),
            strategyTradeDTO.getName(), strategySellInfo.getMatchInfo(), strategyTradeDTO.getBuyPrice());
        if (sendDingtalk) {
            dingTalkService.sendMessage(DING_TALK_URL,
                DateUtil.formatDate(sellDate) + "股票通知:" + strategySellInfo.getMatchInfo(),
                Lists.newArrayList("18057138912"));
        }
        strategyTradeDTO.setSellDate(sellDate);
        strategyTradeDTO.setSellPrice(realtimePrice);
        strategyTradeDTO.setStatus("sell");
        BigDecimal range = realtimePrice.subtract(strategyTradeDTO.getBuyPrice());
        BigDecimal rate = BigDecimalUtils.divideToPrecent(range, strategyTradeDTO.getBuyPrice());
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setProfitAmount(range);
        strategyTradeDTO.setWinFlag(
            BigDecimalUtils.isGt(range, BigDecimal.ZERO) ? BooleanUtils.TRUE : BooleanUtils.FALSE);
        int holdDay = WorkDayUtils.betweenDay(strategyTradeDTO.getBuyDate(), sellDate);
        strategyTradeDTO.setHoldDay(holdDay);
        strategyTradeDTO.setStrategyDescription(strategySellInfo.getMatchInfo());
        //metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTO);
        return strategyTradeDTO;
    }

    private StrategySellInfo checkStrategy(BigDecimal realtimePrice, Date sellDate, StrategyTradeDTO strategyTradeDTO,
        StrategyParamDTO strategyParamDTO, StockDayData stockDayData, List<StockDayData> stockDayDataList, int index,
        BackStrategyParam param) {
        List<String> highDateList = param.getHighDateList();
        StrategySellInfo info = new StrategySellInfo();
        BigDecimal buyPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeMaxPrice = strategyTradeDTO.getRangeMaxPrice();
        BigDecimal fallbackRate = new BigDecimal(strategyParamDTO.getFallBackRate()).abs();
        BigDecimal fallDownRate = new BigDecimal(strategyParamDTO.getFallDownRate()).abs();
        BigDecimal range = realtimePrice.subtract(buyPrice);
        BigDecimal rate = BigDecimalUtils.divideToPrecent(range, buyPrice);
        StockDayData yesterdayDayData = index == 0 ? stockDayDataList.get(0) : stockDayDataList.get(index - 1);
        ////上涨超过120%后盈利处理,遇到在高价日期，就果断卖掉
        //if (BigDecimalUtils.isGe(strategyTradeDTO.getRealtimeRate(), new BigDecimal(120)) && isTopSignal(
        //    strategyTradeDTO.getCode(), stockDayData, param)) {
        //    info.setMatchSell(true);
        //    info.setMatchInfo(
        //        strategyTradeDTO.getName() + "出现到顶信号锤子线,进行止盈,股价:" + strategyTradeDTO.getRealtimePrice() + ",盈利:"
        //            + strategyTradeDTO.getRealtimeRate());
        //    info.setMatchTime(sellDate);
        //    info.setSellPrice(realtimePrice);
        //    return info;
        //}
        //上涨超过100%后盈利处理,遇到在高价日期，就果断卖掉
        if (BigDecimalUtils.isGe(strategyTradeDTO.getRealtimeRate(), new BigDecimal(100))) {
            if (highDateList.contains(DateUtil.formatDate(sellDate))) {
                info.setMatchSell(true);
                info.setMatchInfo(
                    strategyTradeDTO.getName() + "判定为高点日期,进行止盈,股价:" + strategyTradeDTO.getRealtimePrice() + ",盈利:"
                        + strategyTradeDTO.getRealtimeRate());
                info.setMatchTime(sellDate);
                info.setSellPrice(realtimePrice);
                return info;
            }

        }
        //上涨超过50%后盈利处理,遇到成交量放大超过前一天10倍，就果断卖掉
        //出现到顶信号，果断卖掉
        if (BigDecimalUtils.isGe(strategyTradeDTO.getRealtimeRate(), new BigDecimal(50))) {
            Integer yesterdayTradeNum = yesterdayDayData.getTradeNum() * 10;
            if (stockDayData.getTradeNum() >= yesterdayTradeNum) {
                info.setMatchSell(true);
                info.setMatchInfo(
                    strategyTradeDTO.getName() + "出现十倍信号量,进行止盈,股价:" + strategyTradeDTO.getRealtimePrice() + ",盈利:"
                        + strategyTradeDTO.getRealtimeRate());
                info.setMatchTime(sellDate);
                info.setSellPrice(realtimePrice);
                return info;
            }

        }

        //连续3天都低于买入价,果断卖掉
        if (index == 2) {
            List<StockDayData> threeDays = stockDayDataList.subList(1, 4);
            //如果3天内未找到上涨的日期
            StockDayData upData = threeDays.stream().filter(
                data -> BigDecimalUtils.isGt(data.getClosePrice(), buyPrice)).findAny().orElse(null);
            if (Objects.isNull(upData)) {
                info.setMatchSell(true);
                info.setMatchInfo(
                    strategyTradeDTO.getName() + "连续3天小于买入价,进行止损,股价:" + strategyTradeDTO.getRealtimePrice() + ",亏损:"
                        + strategyTradeDTO.getRealtimeRate());
                info.setMatchTime(sellDate);
                info.setSellPrice(realtimePrice);
                return info;
            }
        }
        //10天还没涨超过10%,果断卖掉
        if (index == 9 && BigDecimalUtils.isLt(rate, new BigDecimal(5))) {
            info.setMatchSell(true);
            info.setMatchInfo(
                strategyTradeDTO.getName() + "10天还没涨超过10%,进行止盈,股价:" + strategyTradeDTO.getRealtimePrice() + ",盈利:"
                    + strategyTradeDTO.getRealtimeRate());
            info.setMatchTime(sellDate);
            info.setSellPrice(realtimePrice);
            return info;
        }
        //实时价格高于买入价且最高价回落
        BigDecimal realtimeFallbackRange = rangeMaxPrice.subtract(realtimePrice);
        BigDecimal realtimeFallbackRate = BigDecimalUtils.divideToPrecent(realtimeFallbackRange, realtimePrice);
        if (isFallback(buyPrice, realtimePrice, strategyTradeDTO.getRealtimeRate(), rangeMaxPrice, fallbackRate)) {
            info.setMatchSell(true);
            info.setMatchInfo(strategyTradeDTO.getName() + "最高价回落" + realtimeFallbackRate + "%,进行止盈,股价:"
                + strategyTradeDTO.getRealtimePrice() + ",盈利:" + strategyTradeDTO.getRealtimeRate());
            info.setMatchTime(sellDate);
            info.setSellPrice(realtimePrice);
            return info;
        }
        //买入最低价止损
        if (BooleanUtils.isTrue(strategyParamDTO.getBuyMinSellFlag())) {
            BigDecimal temPrice = strategyTradeDTO.getBuyPrice().multiply(new BigDecimal("0.93"));
            temPrice = BigDecimalUtils.isLt(temPrice, strategyTradeDTO.getBuyMinPrice()) ? temPrice
                : strategyTradeDTO.getBuyMinPrice();
            if (realtimePrice.compareTo(temPrice) < 0) {
                //跌到止损位
                info.setMatchSell(true);
                info.setMatchInfo(
                    strategyTradeDTO.getName() + "跌破买入日最低价" + strategyTradeDTO.getBuyMinPrice() + ",进行止损,股价:"
                        + realtimePrice + ",亏损:" + strategyTradeDTO.getRealtimeRate());
                info.setMatchTime(sellDate);
                info.setSellPrice(realtimePrice);
                return info;
            }
            //根据止损比例止损
        } else if (BigDecimalUtils.isLt(realtimePrice, buyPrice) && BigDecimalUtils.isGe(rate.abs(), fallDownRate)) {
            //跌到止损位
            info.setMatchSell(true);
            info.setMatchInfo(
                strategyTradeDTO.getName() + "到达止损比例" + strategyParamDTO.getFallDownRate() + ",进行止损,股价:" + realtimePrice
                    + ",亏损:" + strategyTradeDTO.getRealtimeRate());
            info.setMatchTime(sellDate);
            info.setSellPrice(realtimePrice);
            return info;
        }

        //if (ShapeUtils.isSellKline(stockDayData) && rate.intValue() > 5) {
        //    info.setMatchSell(true);
        //    info.setMatchInfo(
        //        strategyTradeDTO.getName() + "匹配卖出形态,进行卖出,股价:"
        //            + strategyTradeDTO.getRealtimePrice() + ",盈利:" + strategyTradeDTO.getRealtimeRate());
        //    info.setMatchTime(sellDate);
        //    info.setSellPrice(realtimePrice);
        //    return info;
        //}
        //超期限,且股价在超限幅度之下
        Date buyDate = strategyTradeDTO.getBuyDate();
        Date now = sellDate;
        Date endDay = DateUtil.offsetDay(buyDate, strategyParamDTO.getHoldDay());

        if (now.after(endDay) && rate.compareTo(new BigDecimal(strategyParamDTO.getHoldOnRate())) <= 0) {
            info.setMatchSell(true);
            info.setMatchInfo(strategyTradeDTO.getName() + "到达策略期限,执行卖出,股价:" + realtimePrice + ",盈利:" + rate);
            info.setMatchTime(sellDate);
            info.setSellPrice(realtimePrice);
            return info;
        }
        //期限之前或者超限了但是股价在超限幅度之上，继续持有
        info.setMatchSell(false);
        //如果实时价格更高，把最高价设为当前价格
        if (realtimePrice.compareTo(rangeMaxPrice) > 0) {
            strategyTradeDTO.setRangeMaxPrice(realtimePrice);
        }
        return info;
    }

    /**
     * 判断股票是否出现到顶信号,暂时只判定顶部倒锤子线
     *
     * @param code
     * @param stockDayData
     * @param param
     * @return
     */
    private boolean isTopSignal(String code, StockDayData stockDayData, BackStrategyParam param) {
        List<StockDayData> stockDayDataList = param.getStockDayDataList();
        List<StockDayData> beforeDaydatas = StockUtils.getStockDayDataListBeforeDate(stockDayDataList,
            stockDayData.getDate(), 50);

        boolean invertHammerFlag = invertHammer.match(code, stockDayData, beforeDaydatas);
        return invertHammerFlag;
    }

    /**
     * 连续3天低于30日均线
     *
     * @param stockDayData
     * @param averageDataList
     * @return
     */
    private boolean isLowThanThirtyAverageLine(StockDayData stockDayData, List<HullAverageData> averageDataList) {
        String date = stockDayData.getDate();
        int index = 0;
        for (int i = 0; i < averageDataList.size(); i++) {
            HullAverageData averageData = averageDataList.get(i);
            if (averageData.getDate().equals(date)) {
                index = i;
                break;
            }
        }
        if (index <= 2) {
            return false;
        }
        List<HullAverageData> threeDatas = averageDataList.subList(index - 2, index + 1);
        BigDecimal closePrice = stockDayData.getClosePrice();
        for (HullAverageData averageData : threeDatas) {
            if (BigDecimalUtils.isGe(closePrice, averageData.getPrice())) {
                return false;
            }
        }
        return true;
    }

    private void doBuyStrategy() {
        //doNoting
    }

    /**
     * 最高价回落判定
     *
     * @param buyPrice
     * @param realtimePrice
     * @param realtimeRate
     * @param rangeMaxPrice
     * @param fallbackRate
     * @return
     */
    private boolean isFallback(BigDecimal buyPrice, BigDecimal realtimePrice, BigDecimal realtimeRate,
        BigDecimal rangeMaxPrice, BigDecimal fallbackRate) {
        //最高价回落的价格差
        BigDecimal realtimeFallbackRange = rangeMaxPrice.subtract(realtimePrice);
        //最高价回落比例
        BigDecimal realtimeFallbackRate = BigDecimalUtils.divideToPrecent(realtimeFallbackRange, realtimePrice);
        //盈利状态下
        if (BigDecimalUtils.isGt(realtimePrice, buyPrice)) {
            //回落比例决策
            if (realtimeFallbackRate.compareTo(fallbackRate) >= 0) {
                //回落没到上涨区间的百分之七十,继续等
                BigDecimal rangePrice = rangeMaxPrice.subtract(buyPrice);
                rangePrice = rangeMaxPrice.subtract(rangePrice.multiply(new BigDecimal(0.3)));
                BigDecimal fallbackRated = BigDecimalUtils.divideToPrecent(new BigDecimal(100).subtract(fallbackRate),
                    new BigDecimal(100));
                BigDecimal fallbackPrice = rangeMaxPrice.multiply(fallbackRated);
                //回落区间价的40%和从高点回落设定回落值取更低的那个
                rangePrice = BigDecimalUtils.isLt(rangePrice, fallbackPrice) ? rangePrice : fallbackPrice;
                if (BigDecimalUtils.isLt(rangePrice, realtimePrice)) {
                    return false;
                }
                return true;
            }

        } else {
            //亏损到回落比例直接卖掉
            return realtimeFallbackRate.compareTo(fallbackRate) >= 0;
        }

        return false;
    }

    /**
     * 策略卖出信息
     */
    @Data
    public static class StrategySellInfo {
        private boolean matchSell;

        private String matchInfo;

        private Date matchTime;

        private BigDecimal sellPrice;
    }

    /**
     * 回测参数信息
     */
    @Data
    public static class BackStrategyParam {
        private List<StockDayData> stockDayDataList;

        private List<StockDayData> afterDayDataList;

        private List<HullAverageData> averageDataList;

        private List<String> highDateList;

    }

}